// NBodySimulations.cpp : Defines the entry point for the console application.
//

#include <iostream>
#include <vector>
#include <time.h>
#include <ctime>
#include <SDL.h>
#include "opencl.h"
#include "NBody.h"

const int NPARTICLE = 8192;

#define FPS_LIMIT FALSE
const float FPS_DIV = 1000/60;
int avgFPSCounter = 0;
float avgFPS = 0;

typedef struct border
{
	int minX, maxX,
		  minY, maxY;
	border(int x_, int y_)
	{
		minX = x_;
		maxX = x_;
		minY = y_;
		maxY = y_;
	};
	void setNew(int x_, int y_)
	{
		if (x_ < minX)
			minX = x_;
		else if (x_ > maxX)
			maxX = x_;

		if (y_ < minY)
			minY = y_;
		else if (y_ > maxY)
			maxY = y_;
	}
} border;

cl_float betrag(cl_float a)
{
	if (a < 0)
		return -a;
	return a;
}

int nPartikel = 0;
SDL_Renderer* renderer = NULL;
SDL_Event* event = NULL;
border* global_border = NULL;
SDL_Window* window;
OpenCLWork* worker;
cl_float5* test;
OpenCL* openCL = NULL;
int weightMax = 54,
weightMin = 6,
universeWidth = 3000,
universeHeight = 3000,
maxVel = 50;
int interval = 2;
void initSDL()
{
	SDL_Init(SDL_INIT_VIDEO);

	window = SDL_CreateWindow
		(
		"NBody-Simulation",
		100,
		100,
		600,
		600,
		SDL_WINDOW_SHOWN |
		SDL_WINDOW_RESIZABLE
		);
	renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
	event = new SDL_Event();
}

long start = clock();
char buffer[50];

void SDLpaint(cl_float5* particles, border* borders)
{
	long now = clock();
	float fps = (1000.0 / (now - start));
	avgFPS += fps;
		
	if (avgFPSCounter++ >= interval)
	{
		avgFPS /= interval;
		sprintf_s(buffer, "avgFPS: %.2f", avgFPS);
		SDL_SetWindowTitle(window, buffer);
		avgFPSCounter = 0;

		if (avgFPS < 58 && openCL != NULL)
		{
			nPartikel -= 50*(60/avgFPS);///= 1.1;
			std::vector<OpenCLWork*> worker = openCL->getWorker();
			for (int i = 0; i < worker.size(); i++)
			{
				((NBody*)(worker.at(i)))->change(test, nPartikel);
				//((NBody*)(worker.at(i)));
			}
			std::cout << "Partikel--=" << nPartikel << std::endl;
		}
		else if (avgFPS > 62 && openCL != NULL)
		{
			int j = nPartikel + 100*avgFPS/60.0;//(avgFPS / 60.0);
			cl_float5* newTest = new cl_float5[j];
			for (int i = 0; i < j; i++)
			{
				if (i < nPartikel)
				{
					newTest[i] = test[i];
					continue;
				}

				newTest[i].x = (cl_float)(rand() / (cl_float)0x7fff * universeWidth);
				newTest[i].y = (cl_float)(rand() / (cl_float)0x7fff * universeHeight);
				newTest[i].w = (cl_float)(rand() / (cl_float)0x7fff * weightMax) + weightMin;
				newTest[i].velX = 0;
				newTest[i].velY = 0;
			}
			test = newTest;
			nPartikel = j;
			std::vector<OpenCLWork*> worker = openCL->getWorker();
			for (int i = 0; i < worker.size(); i++)
			{
				((NBody*)(worker.at(i)))->change(test, nPartikel);
				//((NBody*)(worker.at(i)));
			}
			std::cout << "Partikel++=" << nPartikel << std::endl;
		}
		else
			interval++;
	}
	//std::cout << fps << std::endl;
	start = clock();

	if (global_border == NULL)
		global_border = borders;

	/* Draw a gray background */
	SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
	SDL_RenderClear(renderer);
	SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);

	cl_float verhaltnisX = 580.0f / (global_border->maxX - global_border->minX);
	cl_float verhaltnisY = 580.0f / (global_border->maxY - global_border->minY);
	SDL_RenderSetScale(renderer, verhaltnisX, verhaltnisY);


	SDL_Rect* rect;

	for (int i = 1; i < nPartikel; i++) {
		rect = new SDL_Rect();
		rect->x = (int) ((particles[i].x - global_border->minX - particles[i].w));
		rect->y = (int) ((particles[i].y - global_border->minY - particles[i].w));
		rect->w = (int) (particles[i].w);
		rect->h = (int) (particles[i].w);
		
		
		SDL_RenderFillRect(renderer, rect);
		delete rect;
	}
	SDL_RenderPresent(renderer);


	while (SDL_PollEvent(event))
		if (event->type == SDL_MOUSEWHEEL)
		{
			int value = ((SDL_MouseWheelEvent*) event)->y;
			global_border->maxX -= value * 50;
			global_border->maxY -= value * 50;
			
			global_border->minX += value * 50;
			global_border->minY += value * 50;

			//std::cout << value << std::endl;
		}
		//std::cout << event->type << std::endl;

#if FPS_LIMIT
	SDL_Delay((Uint32)FPS_DIV - (now - start));
#endif
}

void initOpenCL()
{
	OpenCL::printPlatforms();

	std::cout << "Bitte Platform ID eingeben: ";

	char t;
	std::cin >> t;

	if ('0' <= t && t <= '9')
		openCL = new OpenCL(t-'0');

	openCL->printDevices();

	std::cout << "Eingabe wird beendet bei einem zeichen auser 0-9: \n";
	std::vector<byte> devices = std::vector<byte>();

	std::cin >> t;
	devices.push_back(t - '0');
	/*
	bool found = false;
	while (!found)
	{
		std::cin >> t;
		if ('0' <= t && t <= '9')
		{
			bool founder = false;
			for (int i = 0; i < devices.size() && !founder; i++)
			{
				if (devices[i] == t - '0')
					founder = true;
			}
			if (!founder)
				devices.push_back(t - '0');
		}
		else if (t == ' ')
			;
		else
			found = true;
	}
	*/
	openCL->selectDevices(devices);

	//std::cin >> t;

}

int main(int argc, char* argv[])
{
	initOpenCL();

		nPartikel = NPARTICLE;


	std::cout << "Wie viele partikel sollen erstellt werden?: ";
	int tempInt;
	std::cin >> tempInt;
	if (tempInt > 0)
	{
		nPartikel = tempInt;
		universeWidth = nPartikel;
		universeHeight = nPartikel;
		weightMin = nPartikel / 200+1;
		weightMax = weightMin;
		

	}

	std::cout << "\n";
	
	initSDL();

	time_t t;
	time(&t);
	srand((unsigned int) t);              /* Zufallsgenerator initialisieren */

	test = new cl_float5[nPartikel];

	test[0].x = (cl_float)(universeWidth/2);
	test[0].y = (cl_float)(universeHeight/2);
	test[0].w = (cl_float)(weightMax + weightMin)*100;
	test[0].velX = 0;
	test[0].velY = 0;

	for (int i = 1; i < nPartikel; i++)
	{
		test[i].x = (cl_float) (rand() / (cl_float) 0x7fff * universeWidth);
		test[i].y = (cl_float) (rand() / (cl_float) 0x7fff * universeHeight);
		test[i].w = (cl_float) (rand() / (cl_float) 0x7fff * weightMax) + weightMin;
		test[i].velX = 0;
		test[i].velY = 0;
	}
	border* borders;
	borders = new border(universeWidth*2, universeHeight*2);
	borders->setNew(-universeWidth, -universeHeight);
	SDLpaint(test, borders);
	
	
	worker = (OpenCLWork*) new NBody( test, nPartikel);
	openCL->setWork(worker);

	std::cout << "test";
	for (;;)
	{
		openCL->next();
		openCL->waitForWorker();

		SDLpaint(test, borders);
	}
	return 0;
}
